package game;

import game.console.Console;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public abstract class AbstractGame implements Runnable
{
    public static synchronized void log(Object message, boolean newLine)
    {
        if (message instanceof Throwable)
        {
            System.out.println(((Throwable) message).getLocalizedMessage());
            ((Throwable) message).printStackTrace(System.out);
        }
        else
        {
            System.out.print(message.toString());
        }
        if (newLine)
        {
            System.out.println();
        }
    }
    
    public static synchronized void log(Object message)
    {
        log(message, true);
    }
    
    private JFrame                 frame;
    private JPanel                 panel;
    
    private int                    desiredFPS   = 20;
    
    private boolean                GAME_RUNNING;
    private final AbstractRunnable GAME_INSTANCE;
    
    protected AbstractGame(AbstractRunnable game)
    {
        GAME_INSTANCE = game;
        // panel
        panel = new JPanel()
        {
            private static final long serialVersionUID = -1923410016305041430L;

            @Override
            protected void paintComponent(Graphics g)
            {
                render(g);
            }
        };
        panel.setLayout(null);
        panel.setIgnoreRepaint(true);
        // frame
        frame = new JFrame("Basic Game");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Rectangle size =  GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds();
        setSize(size.width / 2, size.height / 2);
        frame.setContentPane(panel);
        frame.pack();
        frame.setVisible(true);
//        frame.setResizable(false);
        frame.addComponentListener(new ComponentControl());
        panel.addKeyListener(new KeyControl());
        // game instance
        panel.addKeyListener(GAME_INSTANCE.getKeyboardControl());
        panel.addMouseListener(GAME_INSTANCE.getMouseControl());
        panel.addMouseMotionListener(GAME_INSTANCE.getMouseMotionListener());
        // listener
        panel.requestFocus();
    }
    
    private void setSize(int width, int height)
    {
        panel.setPreferredSize(new Dimension(width, height));
        panel.setBounds(0, 0, width, height);
        Console.getInstance().setSize(width, height / 3);
        if (GAME_INSTANCE instanceof Abstract2DMap)
        {
            ((Abstract2DMap) GAME_INSTANCE).recalcSize(panel.getPreferredSize());
        }
    }
    
    public Dimension getSize()
    {
        return panel.getPreferredSize();
    }
    
    private class KeyControl extends KeyAdapter
    {
        private boolean IGNORE_KEYBOARD       = false;
        
        @Override
        public void keyPressed(KeyEvent e)
        {
            if (IGNORE_KEYBOARD)
            {
                e.consume();
            }
            else
            {
                IGNORE_KEYBOARD = true;
                final Console console = Console.getInstance();
                if (console.consoleKeyPressed(e.getKeyCode()))
                {
                    if (console.isOpen())
                    {
                        console.closeConsole();
                    }
                    else
                    {
                        console.openConsole();
                    }
                    log("CONSOLE OPEN: " + console.isOpen());
                }
                if (console.isOpen())
                {
                    console.fireKeyEvent(e);
                }
                else
                {
                    final int stepsFPS = 5;
                    final int MAX_FPS = 120;
                    final int MIN_FPS = 1;
                    switch (e.getKeyCode())
                    {
                        case KeyEvent.VK_MINUS:
                        {
                            if (desiredFPS <= 10)
                            {
                                desiredFPS--;
                            }
                            else
                            {
                                desiredFPS -= stepsFPS;
                            }
                            if (desiredFPS < MIN_FPS)
                            {
                                desiredFPS = MIN_FPS;
                            }
                            log("-FPS: " + desiredFPS);
                            recalcDesiredDeltaLoop();
                            break;
                        }
                        case KeyEvent.VK_PLUS:
                        {
                            if (desiredFPS < 10)
                            {
                                desiredFPS++;
                            }
                            else
                            {
                                desiredFPS += stepsFPS;
                            }
                            if (desiredFPS > MAX_FPS)
                            {
                                desiredFPS = MAX_FPS;
                            }
                            log("+FPS: " + desiredFPS);
                            recalcDesiredDeltaLoop();
                            break;
                        }
                    }
                }
                IGNORE_KEYBOARD = false;
            }
        }
    }
    
    private class ComponentControl extends ComponentAdapter
    {
        private boolean   isResizing;
        private long      resizeTime   = 0;
        private final int DELAY        = 600;
        
        private Runnable resizerRunnable = new Runnable()
        {
            @Override
            public void run()
            {
                while (true)
                {
                    try
                    {
                        while (isResizing)
                        {
                            long delay = System.currentTimeMillis() - resizeTime;
                            if (delay > DELAY)
                            {
                                setSize(newSize.width, newSize.height);
                                GAME_INSTANCE.requestRestart();
                                isResizing = false;
                            }
                            else
                            {
                                Thread.sleep(delay);
                            }
                        }
                        Thread.sleep(DELAY);
                    }
                    catch (InterruptedException e)
                    {
//                        e.printStackTrace(System.err);
                    }
                }
            }
        };
        
        private Dimension newSize;
        private Thread resizeThread;
        
        public ComponentControl()
        {
            this.resizeThread = new Thread(resizerRunnable, "Resizer");
            resizeThread.start();
        }
        
        @Override
        public void componentResized(ComponentEvent e)
        {
            newSize = e.getComponent().getSize();
            resizeTime = System.currentTimeMillis();
            isResizing = true;
            resizeThread.interrupt();
        }
    }
    
    private int recalcDesiredDeltaLoop()
    {
        return (1000 * 1000 * 1000) / desiredFPS;
    }
    
    @Override
    public void run()
    {
        GAME_RUNNING = true;
        while (GAME_RUNNING)
        {
            try
            {
                long beginLoopTime = System.nanoTime();
                SwingUtilities.invokeAndWait(new Runnable()
                {
                    
                    @Override
                    public void run()
                    {
                        panel.repaint();
                    }
                });
                update();
                long endLoopTime = System.nanoTime();
                long deltaLoop = endLoopTime - beginLoopTime;
                long desiredDeltaLoop = recalcDesiredDeltaLoop();
                if (deltaLoop <= desiredDeltaLoop )
                {
                    try
                    {
                        Thread.sleep((desiredDeltaLoop - deltaLoop) / (1000 * 1000));
                    }
                    catch (InterruptedException e)
                    {
                        // Do nothing
                    }
                }
            }
            catch (Exception ex)
            {
                StringWriter sw = new StringWriter();
                ex.printStackTrace(new PrintWriter(sw));
                log(sw.toString());
                GAME_RUNNING = false;
            }
        }
    }
    
    private void update()
    {
        GAME_INSTANCE.update();
        // title
        StringBuilder title = new StringBuilder(String.valueOf(desiredFPS)).append(" FPS; ");
        title.append(GAME_INSTANCE.getStatus());
        title.append(GAME_INSTANCE.getInfo());
        frame.setTitle(title.toString());
    }
    
    private void render(Graphics g)
    {
        GAME_INSTANCE.render(g);
        Console con = Console.getInstance();
        if (con.isOpen())
        {
            con.render(g);
        }
    }
    
//    private void clearScreen(Graphics2D g, int width, int height)
//    {
//        if (width > this.width)
//        {
//            width = this.width;
//        }
//        if (height > this.height)
//        {
//            height = this.height;
//        }
//        Color color = g.getColor();
//        g.setColor(Color.WHITE);
//        g.fillRect(0, 0, width, height);
//        g.setColor(color);
//    }
//    
//    private void renderCenterText(Graphics g, String s, int fontSize)
//    {
//        g.setColor(Color.BLACK);
//        g.setFont(new Font("Monospaced", Font.PLAIN, fontSize));
//        int w2 = g.getFontMetrics().stringWidth(s) / 2;
//        int h2 = g.getFontMetrics().getDescent();
//        g.drawString(s, width / 2 - w2, height / 2 + h2);
//    }
    
}
